/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "sql_statement.h"
#include <algorithm>
#include "node/log/logger.h"
#include "node/log/logsystem.h"
#include "node/db/database.h"
#include "node/db/connection.h"

#pragma warning(disable:4996)

namespace node {
    namespace db {

        SqlStmtParameters::SqlStmtParameters(::std::uint32_t nParams)
        {
            if (nParams > 0)
            { params_.reserve(nParams); }
        }

        void SqlStmtParameters::reset(const SqlStatement& stmt)
        {
            params_.clear();
            if (stmt.arguments() > 0)
            { params_.reserve(stmt.arguments()); }
        }

        //////////////////////////////////////////////////////////////////////////
        SqlStatement& SqlStatement::operator=(const SqlStatement& index)
        {
            if (this != &index)
            {
                index_ = index.index_;
                db_ = index.db_;

                delete params_;
                params_ = NULL;

                if (index.params_)
                { params_ = new SqlStmtParameters(*(index.params_)); }
            }

            return *this;
        }

        bool SqlStatement::execute()
        {
            SqlStmtParameters* args = detach();
            if (args->boundParams() != arguments())
            {
                NODE_LOGGER("main").error("SQL ERROR: wrong amount of parameters (%i instead of %i)", args->boundParams(), arguments());
                NODE_LOGGER("main").error("SQL ERROR: statement: %s", db_->getStmtString(id()).c_str());
                NODE_ASSERT(false);
                return false;
            }

            return db_->executeStmt(index_, args);
        }

        bool SqlStatement::directExecute()
        {
            SqlStmtParameters* args = detach();
            // verify amount of bound parameters
            if (args->boundParams() != arguments())
            {
                NODE_LOGGER("main").error("SQL ERROR: wrong amount of parameters (%i instead of %i)", args->boundParams(), arguments());
                NODE_LOGGER("main").error("SQL ERROR: statement: %s", db_->getStmtString(id()).c_str());
                NODE_ASSERT(false);
                return false;
            }

            return db_->directExecuteStmt(index_, args);
        }

        //////////////////////////////////////////////////////////////////////////
        SqlPlainPreparedStatement::SqlPlainPreparedStatement(const std::string& fmt, Connection& conn) : SqlPreparedStatement(fmt, conn)
        {
            prepared_ = true;
            params_ = std::count(format_.begin(), format_.end(), '?');
            query_ = strnicmp(format_.c_str(), "select", 6) == 0;
        }

        void SqlPlainPreparedStatement::bind(const SqlStmtParameters& holder)
        {
            // verify if we bound all needed input parameters
            if (params_ != holder.boundParams())
            {
                NODE_ASSERT(false);
                return;
            }

            // reset resulting plain SQL request
            plain_request_ = format_;
            size_t nLastPos = 0;

            SqlStmtParameters::ParameterContainer const& _args = holder.params();

            SqlStmtParameters::ParameterContainer::const_iterator iter_last = _args.end();
            for (SqlStmtParameters::ParameterContainer::const_iterator iter = _args.begin(); iter != iter_last; ++iter)
            {
                // bind parameter
                const SqlStmtFieldData& data = (*iter);

                std::ostringstream fmt;
                dataToString(data, fmt);

                nLastPos = plain_request_.find('?', nLastPos);
                if (nLastPos != std::string::npos)
                {
                    std::string tmp = fmt.str();
                    plain_request_.replace(nLastPos, 1, tmp);
                    nLastPos += tmp.length();
                }
            }
        }

        bool SqlPlainPreparedStatement::execute()
        {
            if (plain_request_.empty())
            { return false; }

            return conn_.execute(plain_request_.c_str());
        }

        void SqlPlainPreparedStatement::dataToString(const SqlStmtFieldData& data, std::ostringstream& fmt)
        {
            switch (data.type())
            {
            case FIELD_BOOL:    fmt << "'" << ::std::uint32_t(data.toBool()) << "'";     break;
            case FIELD_UI8:     fmt << "'" << ::std::uint32_t(data.toUint8()) << "'";    break;
            case FIELD_UI16:    fmt << "'" << ::std::uint32_t(data.toUint16()) << "'";   break;
            case FIELD_UI32:    fmt << "'" << data.toUint32() << "'";           break;
            case FIELD_UI64:    fmt << "'" << data.toUint64() << "'";           break;
            case FIELD_I8:      fmt << "'" << ::std::int32_t(data.toInt8()) << "'";      break;
            case FIELD_I16:     fmt << "'" << ::std::int32_t(data.toInt16()) << "'";     break;
            case FIELD_I32:     fmt << "'" << data.toInt32() << "'";            break;
            case FIELD_I64:     fmt << "'" << data.toInt64() << "'";            break;
            case FIELD_FLOAT:   fmt << "'" << data.toFloat() << "'";            break;
            case FIELD_DOUBLE:  fmt << "'" << data.toDouble() << "'";           break;
            case FIELD_STRING:
                {
                    std::string tmp = data.toStr();
                    conn_.db().escapeString(tmp);
                    fmt << "'" << tmp << "'";
                    break;
                }
            case FIELD_NONE:                                                    break;
            }
        }

    }
}